create or replace package body m4o_buffer_impl as

  C_MODULE constant m4o_module := new m4o_module('m4o_buffer_impl');

  type t_buffer is record (
    data      m4o_v2.tbl_t,
    subst     m4o_v2.map_t,
    start_tag varchar2(32767),
    end_tag   varchar2(32767)
  );

  type tt_buffer is table of t_buffer index by binary_integer;

  g_buffers tt_buffer;
  g_max     binary_integer;

  -- ===========================================================================
  procedure check_handle_is_valid(i_handle in t)
  is
    v_pc m4o_context := C_MODULE.proc('check_handle_is_valid');
  begin
    if not g_buffers.exists(i_handle) then
      v_pc.error('no buffer for handle "'||i_handle||'"');
    end if;
  end;

  -- ===========================================================================
  function fmt(
    i_buffer in t_buffer,
    i_str    in varchar2)
  return varchar2
  is
    v_pc        m4o_context := C_MODULE.proc('fmt');
    v_startpos  pls_integer;
    v_endpos    pls_integer;
    v_str       varchar2(32767);
    v_subst_arg varchar2(32767);
  begin
    if i_str is null then
      null;
    elsif i_buffer.subst.count=0 then
      v_str := i_str;
    else
      v_endpos   := 1;

      loop
        v_startpos := instr(i_str,i_buffer.start_tag,v_endpos);

        if v_startpos = 0 then
          v_str := v_str || substr(i_str,v_endpos);
          exit;
        else
          -- copy data from last tag end up to the found tag start
          v_str := v_str || substr(i_str,v_endpos,v_startpos-v_endpos);

          -- advance to 1 after end of start tag
          v_startpos := v_startpos + length(i_buffer.start_tag);
          -- find next end tag from there
          v_endpos   := instr(i_str,i_buffer.end_tag,v_startpos);

          if v_endpos = 0 then
            v_pc.error('end tag "'
                     ||i_buffer.end_tag
                     ||'" not found at pos '
                     ||v_startpos);
          elsif v_endpos = v_startpos then
            -- start||end -> write starttag
            v_str := v_str || i_buffer.start_tag;
          else
            -- a subst arg found at v_startpos
            v_subst_arg := substr(i_str,v_startpos,v_endpos-v_startpos);

            if not i_buffer.subst.exists(v_subst_arg) then
              v_pc.error('substitution variable "'
                       ||v_subst_arg
                       ||'" not defined in buffer');
            else
              v_str := v_str || i_buffer.subst(v_subst_arg);
            end if;
          end if;

          v_endpos := v_endpos + length(i_buffer.end_tag);
        end if;
      end loop;
    end if;

    return v_str;
  end;
  -- ===========================================================================
  function fmt(
    i_handle in t,
    i_str    in varchar2)
   return varchar2
   is
   begin
     return fmt(g_buffers(i_handle),i_str);
   end;

  -- ===========================================================================
  function create_handle(
    i_subst_start_tag in varchar2,
    i_subst_end_tag   in varchar2)
  return t
  is
    v_pc m4o_context := C_MODULE.proc('create_handle');
    v_buf t_buffer;
  begin
    v_buf.start_tag := i_subst_start_tag;
    v_buf.end_tag   := i_subst_end_tag;

    g_max := nvl(g_max,0)+1;
    g_buffers(g_max) := v_buf;

    return g_max;
  end;

  -- ===========================================================================
  procedure free_handle(i_handle in t)
  is
    v_pc m4o_context := C_MODULE.proc('free_handle',
                          m4o_v2tab(i_handle));
  begin
    if i_handle is null then
      v_pc.do('handle was null - deleting all buffers');
      g_buffers.delete;
      g_max := 0;
    else
      check_handle_is_valid(i_handle);

      v_pc.do('freeing handle '||i_handle);
      g_buffers.delete(i_handle);
      if i_handle = g_max then
        g_max := g_max - 1;
      end if;
    end if;
  end;

  -- ===========================================================================
  procedure p(
    i_handle in t,
    i_str    in varchar2,
    i_fmt    in boolean)
  is
    v_pc m4o_context := C_MODULE.proc('p',
                          m4o_v2tab(i_handle,i_str));
    v_text  varchar2(32767);
    v_start pls_integer;
    v_crpos pls_integer;
    procedure w(i_new_str in varchar2)
    is
    begin
      g_buffers(i_handle).data(g_buffers(i_handle).data.count+1) := i_new_str;
    end;
  begin
    check_handle_is_valid(i_handle);

    if i_str is null then
      w(i_str);
    else
      v_text :=
        case
        when i_str is null then null
        when i_fmt         then fmt(g_buffers(i_handle),i_str)
        else                    i_str
        end;

      v_start := 1;
      loop
        v_crpos := instr(v_text,m4o_v2.CR,v_start);

        if v_crpos = 0 then
          w(substr(v_text,v_start));
          exit;
        else
          w(substr(v_text,v_start,v_crpos-v_start));
          v_start := v_crpos+length(m4o_v2.CR);
        end if;
      end loop;
    end if;
  end;

  -- ===========================================================================
  procedure set_subst(
    i_handle in t,
    i_key    in varchar2,
    i_value  in varchar2)
  is
    v_pc m4o_context := C_MODULE.proc('set_subst',
                          m4o_v2tab(i_handle,i_key,i_value));
  begin
    check_handle_is_valid(i_handle);

    g_buffers(i_handle).subst(i_key) := i_value;
  end;
  -- ===========================================================================
  function get_str(i_handle in t) return varchar2
  is
    function concat_data(i_data in m4o_v2.tbl_t)
    return varchar2
    is
      v_str varchar2(32767);
    begin
      v_str := i_data(1);
      for i in 2 .. i_data.count loop
        v_str := v_str
              || m4o_v2.CR
              || i_data(i);
      end loop;
      return v_str;
    end;
  begin
    check_handle_is_valid(i_handle);

    return
      case g_buffers(i_handle).data.count
      when 0 then null
      when 1 then g_buffers(i_handle).data(1)
      else concat_data(g_buffers(i_handle).data)
      end;
  end;

  -- ===========================================================================
  function get_buf(i_handle in t) return m4o_v2.tbl_t
  is
  begin
    check_handle_is_valid(i_handle);

    return g_buffers(i_handle).data;
  end;

  -- ===========================================================================
  procedure clear_buffer(
    i_handle      in t,
    i_clear_subst in boolean := false)
  is
  begin
    check_handle_is_valid(i_handle);

    g_buffers(i_handle).data.delete;
  end;

end;
/

